package org.openstreetmap.josm.plugins.graphview.core.util;

import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class ValueStringParser {

	/** prevents instantiation */
	private ValueStringParser() { }

	/** pattern that splits into a part before and after a decimal point */
	private static final Pattern DEC_POINT_PATTERN = Pattern.compile("^(\\-?\\d+)\\.(\\d+)$");

	public static final Float parseOsmDecimal(String value, boolean allowNegative) {

		/* positive integer */

		try {

			int weight = Integer.parseInt(value);
			if (weight >= 0 || allowNegative) {
				return (float)weight;
			}

		} catch (NumberFormatException nfe) {}

		/* positive number with decimal point */

		Matcher matcher = DEC_POINT_PATTERN.matcher(value);

		if (matcher.matches()) {

			String stringBeforePoint = matcher.group(1);
			String stringAfterPoint = matcher.group(2);

			if (stringBeforePoint.length() > 0 || stringAfterPoint.length() > 0) {

				try {

					boolean negative = stringBeforePoint.startsWith("-");
					
					float beforePoint = Integer.parseInt(stringBeforePoint);
					float afterPoint = Integer.parseInt(stringAfterPoint);

					double result = Math.abs(beforePoint)
							+ Math.pow(10, -stringAfterPoint.length()) * afterPoint;
					if (negative) { result = - result; }
					
					if (result >= 0 || allowNegative) {
						return (float)result;
					}

				} catch (NumberFormatException nfe) {}

			}
		}

		return null;
	}

	private static final Pattern KMH_PATTERN = Pattern.compile("^(\\d+)\\s*km/h$");
	private static final Pattern MPH_PATTERN = Pattern.compile("^(\\d+)\\s*mph$");

	private static final float KM_PER_MILE = 1.609344f;

	/**
	 * parses a speed value given e.g. for the "maxspeed" key.
	 *
	 * @return  speed in km/h; null if value had syntax errors
	 */
	public static final Float parseSpeed(String value) {

		/* try numeric speed (implied km/h) */

		Float speed = parseOsmDecimal(value, false);
		if (speed != null) {
			return speed;
		}

		/* try km/h speed */

		Matcher kmhMatcher = KMH_PATTERN.matcher(value);
		if (kmhMatcher.matches()) {
			String kmhString = kmhMatcher.group(1);
			try {
				return (float)Integer.parseInt(kmhString);
			} catch (NumberFormatException nfe) {}
		}

		/* try mph speed */

		Matcher mphMatcher = MPH_PATTERN.matcher(value);
		if (mphMatcher.matches()) {
			String mphString = mphMatcher.group(1);
			try {
				int mph = Integer.parseInt(mphString);
				return KM_PER_MILE * mph;
			} catch (NumberFormatException nfe) {}
		}

		/* all possibilities failed */

		return null;
	}

	private static final Pattern M_PATTERN = Pattern.compile("^([\\d\\.]+)\\s*m$");
	private static final Pattern KM_PATTERN = Pattern.compile("^([\\d\\.]+)\\s*km$");
	private static final Pattern MI_PATTERN = Pattern.compile("^([\\d\\.]+)\\s*mi$");
	private static final Pattern FEET_INCHES_PATTERN = Pattern.compile("^([\\d]+)'\\s*([\\d]+)\"");

	private static final double M_PER_MI = 1609.344;
	private static final double M_PER_INCH = 0.0254f;

	/**
	 * parses a measure value given e.g. for the "width" or "length" key.
	 *
	 * @return  measure in m; null if value had syntax errors
	 */
	public static final Float parseMeasure(String value) {

		/* try numeric measure (implied m) */

		Float measure = parseOsmDecimal(value, false);
		if (measure != null) {
			return measure;
		}

		/* try m measure */

		Matcher mMatcher = M_PATTERN.matcher(value);
		if (mMatcher.matches()) {
			String mString = mMatcher.group(1);
			return parseOsmDecimal(mString, false);
		}

		/* try km measure */

		Matcher kmMatcher = KM_PATTERN.matcher(value);
		if (kmMatcher.matches()) {
			String kmString = kmMatcher.group(1);
			float km = parseOsmDecimal(kmString, false);
			return 1000 * km;
		}

		/* try mi measure */

		Matcher miMatcher = MI_PATTERN.matcher(value);
		if (miMatcher.matches()) {
			String miString = miMatcher.group(1);
			float mi = parseOsmDecimal(miString, false);
			return (float)(M_PER_MI * mi);
		}

		/* try feet/inches measure */

		Matcher feetInchesMatcher = FEET_INCHES_PATTERN.matcher(value);
		if (feetInchesMatcher.matches()) {
			String feetString = feetInchesMatcher.group(1);
			String inchesString = feetInchesMatcher.group(2);
			try {
				int feet = Integer.parseInt(feetString);
				int inches = Integer.parseInt(inchesString);
				if (feet >= 0 && inches >= 0 && inches < 12) {
					return (float)(M_PER_INCH * (12 * feet + inches));
				}
			} catch (NumberFormatException nfe) {}
		}

		/* all possibilities failed */

		return null;
	}

	private static final Pattern T_PATTERN = Pattern.compile("^([\\d\\.]+)\\s*t$");

	/**
	 * parses a weight value given e.g. for the "maxweight" or "maxaxleload" key.
	 *
	 * @return  weight in t; null if value had syntax errors
	 */
	public static Float parseWeight(String value) {

		/* try numeric weight (implied t) */

		Float weight = parseOsmDecimal(value, false);
		if (weight != null) {
			return weight;
		}

		/* try t weight */

		Matcher tMatcher = T_PATTERN.matcher(value);
		if (tMatcher.matches()) {
			String tString = tMatcher.group(1);
			return parseOsmDecimal(tString, false);
		}

		/* all possibilities failed */

		return null;

	}

	private static final Pattern INCLINE_PATTERN = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)\\s*%$");

	/**
	 * parses an incline value as given for the "incline" key.
	 *
	 * @return  incline in percents; null if value had syntax errors
	 */
	public static final Float parseIncline(String value) {

		Matcher inclineMatcher = INCLINE_PATTERN.matcher(value);
		if (inclineMatcher.matches()) {
			String inclineString = inclineMatcher.group(1);
			return parseOsmDecimal(inclineString, true);
		}

		return null;
	}

	/**
	 * parses an angular value as given for the "direction" key.
	 *
	 * @return  angle in degrees measured from north, range [0, 360[;
	 *          null if value had syntax errors
	 */
	public static final Float parseAngle(String value) {

		/* try numeric angle */
		
		Float measure = parseOsmDecimal(value, false);
		if (measure != null) {
			return measure % 360;
		}
		
		/* try cardinal directions (represented by letters) */

		if ("N"  .equals(value)) { return   0.0f; }
		if ("NNE".equals(value)) { return  22.5f; }
		if ("NE" .equals(value)) { return  45.0f; }
		if ("ENE".equals(value)) { return  67.5f; }
		if ("E"  .equals(value)) { return  90.0f; }
		if ("ESE".equals(value)) { return 112.5f; }
		if ("SE" .equals(value)) { return 135.0f; }
		if ("SSE".equals(value)) { return 157.5f; }
		if ("S"  .equals(value)) { return 180.0f; }
		if ("SSW".equals(value)) { return 202.5f; }
		if ("SW" .equals(value)) { return 225.0f; }
		if ("WSW".equals(value)) { return 247.5f; }
		if ("W"  .equals(value)) { return 270.0f; }
		if ("WNW".equals(value)) { return 292.5f; }
		if ("NW" .equals(value)) { return 315.0f; }
		if ("NNW".equals(value)) { return 337.5f; }
		
		return null;
	}

	/**
	 * parses an hexadecimal color value
	 *
	 * @return  color; null if value had syntax errors
	 */
	public static final Color parseColor(String value) {
		
		try {
			return Color.decode(value);
		} catch (NumberFormatException e) {
			return null;
		}
		
	}
	
}
